<?php

class YTDownloader {

    private $cache_dir;

	private $cookie_dir;

	private $itag_info = array(

	// Full Video

    18 => "MP4[640x360]",

	22 => "HD MP4[1280x720]",

	36 => "3GP[320x180]",

	43 => "WEBM[640x360]",	

	17 => "3GP[176x144]",

    // DASH videos

    137 => "(Video Only) MP4[1920x1080]",

    248 => "(Video Only) WEBM[1920x1080]",

    136 => "(Video Only) MP4[1280x720]",

    247 => "(Video Only) WEBM[1280x720]",

    135 => "(Video Only) MP4[854x480]",

    244 => "(Video Only) WEBM[854x480]",

    134 => "(Video Only) MP4[640x360]",

    243 => "(Video Only) WEBM[640x360]",

    133 => "(Video Only) MP4[320x240]",

    242 => "(Video Only) WEBM[320x240]",

    160 => "(Video Only) MP4[176x144]",

    278 => "(Video Only) WEBM[176x144]",

    // Dash Audios

    140 => "(Audio Only) M4A[128Kbps]",

    171 => "(Audio Only) WEBM[128Kbps]",

    249 => "(Audio Only) WEBM[50Kbps]",

    250 => "(Audio Only) WEBM[70Kbps]",

    251 => "(Audio Only) WEBM[160Kbps]"

	);


  private $itag_ext = array(

    // Full Video

    18 => ".mp4",

    22 => ".mp4",

    36 => ".3gp",

    43 => ".webm",  

    17 => ".3gp",

    // DASH videos

    137 => ".mp4",

    248 => ".webm",

    136 => ".mp4",

    247 => ".webm",

    135 => ".mp4",

    244 => ".webm",

    134 => ".mp4",

    243 => ".webm",

    133 => ".mp4",

    242 => ".webm",

    160 => ".mp4",

    278 => ".webm",

    // Dash Audios

    140 => ".mp4",

    171 => ".webm",

    249 => ".webm",

    250 => ".webm",

    251 => ".webm"

  );

	

	function __construct(){

		$this->cache_dir = dirname(__FILE__).'/.cache';

		$this->cookie_dir = sys_get_temp_dir();

                if(!file_exists($this->cache_dir)) {

                    mkdir($this->cache_dir,0755);

                  }

	}

    public function getDownloadLinks($id)
    {

        $returnData = FALSE;
        
        $videoID = $this->extractId($id);

        $webPage = $this->curlGet('https://www.youtube.com/watch?v='.$videoID);      

        $sts = null;

        if(preg_match('|"sts":([0-9]{4,}),"|i', $webPage, $matches)) {

            $sts = $matches[1];

        }

        foreach(array('vevo', 'embedded', 'detailpage') as $elKey) {

            $query = http_build_query(array(

                'c' => 'web',

                'el' => $elKey,

                'hl' => 'en_US',

                'sts' => $sts,

                'cver' => 'html5',

                'eurl' => "https://youtube.googleapis.com/v/{$videoID}",

                'html5' => '1',

                'iframe' => '1',

                'authuser' => '1',

                'video_id' => $videoID,

            ));

          

            if($this->is_Ok($videoData = $this->curlGet("http://www.youtube.com/get_video_info?{$query}"))) {

                parse_str($videoData, $videoData);

                break;
            }

        }

        if(isset($videoData['status']) && $videoData['status'] !== 'fail') {
            
            $vInfo['Title'] = $videoData['title'];

            $vInfo['ChannelName'] = $videoData['author'];

            $vInfo['ChannelId'] = $videoData['ucid'];

            $vInfo['Thumbnail'] = str_replace('default', 'maxresdefault', $videoData['thumbnail_url']);

            $vInfo['Duration'] = $videoData['length_seconds'];

            $vInfo['Rating'] = $videoData['avg_rating'];

        }

        if (isset($videoData['url_encoded_fmt_stream_map']) && isset($videoData['adaptive_fmts'])) {
            
            $draft1 = explode(',',$videoData['url_encoded_fmt_stream_map']);
            $draft2 = explode(',',$videoData['adaptive_fmts']);

            foreach ($draft1 as $key) {
                $draftLink[] = $key;
            }

            foreach ($draft2 as $key) {
                $draftLink[] = $key;
            }

            foreach($draftLink as $dlink) {

                parse_str($dlink,$mLink[]);

            }

            if (isset($mLink[0]['s'])) {
                $instructions = $this->get_instructions($webPage);
            }

            foreach($mLink as $linker) {

                if(isset($linker['s'])) {

                    $linkData[] = array(

                        'url' => preg_replace('@(https\:\/\/)[^\.]+(\.googlevideo\.com)@', 'https://redirector$2', $linker['url']).'&signature='.$this->sig_decipher($linker['s'], $instructions).'&title='.$this->clean_name($videoData['title']),

                        'itag' => $linker['itag'],

                        'type' => isset($this->itag_info[$linker['itag']]) ? $this->itag_info[$linker['itag']] : 'Unknown'

                    );

                } else {

                    $linkData[] = array(

                        'url' => preg_replace('@(https\:\/\/)[^\.]+(\.googlevideo\.com)@', 'https://redirector$2', $linker['url']).'&title='.$this->clean_name($videoData['title']),

                        'itag' => $linker['itag'],

                        'type' => isset($this->itag_info[$linker['itag']]) ? $this->itag_info[$linker['itag']] : 'Unknown'

                    );
               }

            }
        }

        if (!empty($vInfo)) {
            $returnData['info'] = $vInfo;
        }

        if (!empty($linkData)) {
            $returnData['dl'] = $linkData;
        }

        return $returnData;
    }


    protected function curlGet($url)
    {
    
        if(in_array('curl', get_loaded_extensions())){
            $ch = curl_init($url);
        
            curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0');
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_HEADER, 0);
            //curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        
            $result = curl_exec($ch);
            curl_close($ch);
        
            return $result;
        }

        if(ini_get('allow_url_fopen')){
            return @file_get_contents($url);
        }
        
        return FALSE;
    }

    private function is_Ok($var) {

        if(!preg_match('|status=fail|i',$var)) {

            return true;

        }

    }

    private function extractId($str)
    {
        
        if(preg_match('/[a-z0-9_-]{11}/i', $str, $matches)){
            return $matches[0];
        }
        
        return FALSE;
    }

    private function get_instructions($html) {

        $playerPattern = '/"assets":.+?"js":\s*("[^"]+")/';

        if(preg_match($playerPattern, $html, $matches) && is_string($_player = json_decode($matches[1])) && strlen($_player) >= 1) {

            $playerLink = substr($_player, 0, 2) == '//' ? "https:{$_player}" : "https://www.youtube.com{$_player}";

            $cache_player = $this->cache_dir.'/.ht-'.md5($_player);

            if(file_exists($cache_player)) {

                return unserialize(file_get_contents($cache_player));

            } else {

                $js_code = $this->curlGet($playerLink);

                $instructions = $this->sig_js_decode($js_code);

                if($instructions){

                    file_put_contents($cache_player, serialize($instructions));

                    return $instructions;

                }
            }
        }

        return false;
    }

    private function clean_name($name)
    {

        $special_chars = array(".","?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%", "+", chr(0));

        $filename = str_replace($special_chars,' ',$name);
        $filename = preg_replace( "#\x{00a0}#siu", ' ', $filename );
        $filename = str_replace( array( '%20', '+', ' '), '-', $filename );
        $filename = preg_replace( '/[\r\n\t -]+/', '-', $filename );
        $filename = trim( $filename, '.-_' );

        return $filename;

    }

    private function sig_decipher($signature, $instructions)
    {

	   foreach($instructions as $opt){

	       $command = $opt[0];

	       $value = $opt[1];

	       if($command == 'swap'){

	       $temp = $signature[0];

	       $signature[0] = $signature[$value % strlen($signature)];

	       $signature[$value] = $temp;

	       } elseif($command == 'splice'){

	       $signature = substr($signature, $value);

	       } elseif($command == 'reverse'){

	       $signature = strrev($signature);

	       }

	   }

	   return trim($signature);

	}

    private function sig_js_decode($file){
    
    	$script     =   $this->getBetween($file, 'a=a.split("");', ';return a.join("")');
        $script     =   str_replace(array("a,","\n"), array(',',''), $script);
        $script2    =   $this->getBetween($file, 'var ' . substr($script, 0, 2).'={', '};');
        $script2    =   str_replace('a,b', 'a', $script2);
        $script     =   str_replace(substr($script, 0, 2).'.', '', $script);
        $script     =   str_replace('(', '', $script);
        $script     =   str_replace(')', '', $script);
        $script_ex  =   explode(";", $script);
        $script2_ex =   explode("\n", $script2);

        for($i = 0; $i < count($script2_ex); $i++) {
            $tmp = isset($script2_ex[$i]) ? explode(':', $script2_ex[$i]) : [];
            $n   = isset($tmp[0]) ? $tmp[0] : '';
            $m   = isset($tmp[1]) ? $tmp[1] : '';
            $tempS[$n] = $m;
        }

        for($i = 0; $i < count($script_ex); $i++) {
            $tmp = isset($script_ex[$i]) ? explode(',', $script_ex[$i]) : [];
            $a = isset($tmp[0]) ? $tmp[0] : '';
            $b = isset($tmp[1]) ? $tmp[1] : '';
            $deKey[] = $this->createCommad($a, $b, $tempS);
        }

        return $deKey;
    }

    private function createCommad($value, $num, $source)
    {
        $result = '';

        if (isset($source[$value]) && mb_strpos($source[$value], 'reverse')) {
            $result = array('reverse', '');
        } elseif (isset($source[$value]) && mb_strpos($source[$value], 'a.splice')) {
            $result = array('splice', $num);
        } else {
            $result = array('swap', $num);
        }

        return $result;
    }

    private function getBetween($content, $start, $end)
    {
        $r = explode($start, $content);

        if (isset($r[1])) {
            $r = explode($end, $r[1]);
            return $r[0];
        }
    
    return '';

    }

}
